package org.jspresso.framework.application.frontend.action;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.jspresso.framework.action.ActionBusinessException;
import org.jspresso.framework.action.IAction;
import org.jspresso.framework.action.IActionHandler;
import org.jspresso.framework.application.backend.persistence.hibernate.HibernateBackendController;
import org.jspresso.framework.application.backend.persistence.hibernate.TestHibernateBackendController;
import org.jspresso.framework.application.backend.session.EMergeMode;
import org.jspresso.framework.model.component.IComponent;
import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.model.BackActionTest;
import org.jspresso.model.TestEntity;
import org.jspresso.model.TestScope;
import org.jspresso.tester.utils.TestEntitySerializer;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;

public class RunSelectedBackActionTestFrontAction<E,F,G> extends FrontendAction<E,F,G> implements ApplicationContextAware {

	ApplicationContext appContext;
	IAction toTestEnvironnementAction;
	IAction toNormalEnvironnementAction;

	@Override
	public boolean execute(IActionHandler actionHandler, final Map<String, Object> context) {
		final BackActionTest backAction = getSelectedModel(context);

		if(!(getBackendController(context) instanceof TestHibernateBackendController)) {
			throw new ActionBusinessException("Cannot run without a TestHibernateBackendController");
		}

		if(backAction != null) {

			if(!backAction.isPersistent()) {
				throw new ActionBusinessException("BackAction not persisted","error.runselectedBackActionTest.backActionNotPersisted");
			}

			// Load the TestEntities for the selected scope
			TestScope testScope = backAction.getTestScope();
			if(testScope == null) { // Get the default scope
				testScope = TestEntitySerializer.getDefaultScope((HibernateBackendController) getBackendController(context));
			}

			//			TestEntitySerializer.getDefaultScope((HibernateBackendController) getBackendController(context)).retrieveScopeContent((HibernateBackendController) getBackendController(context));
			final List<TestEntity> testEntities = testScope.retrieveScopeContent((HibernateBackendController) getBackendController(context));

			// Switch data-source to test one
			actionHandler.execute(toTestEnvironnementAction, context);
			//			((HibernateBackendController) getBackendController(context)).getHibernateSession().flush();
			//			TestEntitySerializer.getDefaultScope((HibernateBackendController) getBackendController(context)).retrieveScopeContent((HibernateBackendController) getBackendController(context));

			// Purge data source content
			((TestHibernateBackendController) getBackendController(context)).clearTestDataSourceContent();

			for(IEntity entity : testEntities) {
				entity.straightSetProperty(IEntity.VERSION, null);
				getBackendController(context).merge(entity, EMergeMode.MERGE_CLEAN_EAGER);
			}
			testScope.straightSetProperty(IEntity.VERSION, null);
			getBackendController(context).merge(testScope, EMergeMode.MERGE_CLEAN_EAGER);


			// Insert all Entities from loaded TestEntities
			final TestScope txTestScope = testScope;
			getBackendController(context).getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
				@Override
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					getBackendController(context).registerForUpdate(getBackendController(context).cloneInUnitOfWork(txTestScope));
					for(TestEntity testEntity : getBackendController(context).cloneInUnitOfWork(testEntities)) {
						getBackendController(context).registerForUpdate(testEntity);
					}
				}
			});

			// This must be done in 2 phases, as the retrieval of the TestEntity's corresponding entity will load and merge objects with their Version number, 
			// which we want to remove here to let hibernate believe this is a new entity (which is the case regarding the persistent context which is empty)
			final List<IComponent> entitiesTosave = new ArrayList<IComponent>();
			final List<IEntity> entitiesMergedTosave = new ArrayList<IEntity>();

			for(TestEntity testEntity : testEntities) {
				IComponent entityToSave = testEntity.getValue();
				entitiesTosave.add(entityToSave);
			}
			for(IComponent entityToSave : entitiesTosave) {
				if(entityToSave instanceof IEntity) {
					entityToSave.straightSetProperty(IEntity.VERSION, null);
					entitiesMergedTosave.add(getBackendController(context).merge((IEntity)entityToSave, EMergeMode.MERGE_CLEAN_EAGER));
				}
			}


			getBackendController(context).getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
				@Override
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					for(IEntity entity : entitiesMergedTosave) {
						getBackendController(context).registerForUpdate(getBackendController(context).cloneInUnitOfWork((IEntity)entity));
					}
					System.out.println("end");
				}
			});

			// Run the test
			backAction.runAssert(appContext);	

			BackActionTest nextAction = backAction.getNextAction();
			while(nextAction != null) {
				nextAction.runAssert(appContext);
				nextAction = nextAction.getNextAction();
			}

			// Retrieve result
			final List<TestEntity> resultTestEntities = testScope.retrieveScopeContent((HibernateBackendController) getBackendController(context));

			// Switch data-source to normal one
			actionHandler.execute(toNormalEnvironnementAction, context);

			// Update the BackActionTest and reinsert new result TestEntities if any
			getBackendController(context).getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
				@Override
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					for(TestEntity testEntity : getBackendController(context).cloneInUnitOfWork(resultTestEntities)) {
						getBackendController(context).registerForUpdate(testEntity);
					}
					getBackendController(context).registerEntity(getBackendController(context).cloneInUnitOfWork(backAction));
				}
			});

		}
		return super.execute(actionHandler, context);
	}

	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		this.appContext = arg0;		
	}

	public void setToTestEnvironnementAction(IAction toTestEnvironnementAction) {
		this.toTestEnvironnementAction = toTestEnvironnementAction;
	}

	public void setToNormalEnvironnementAction(IAction toNormalEnvironnementAction) {
		this.toNormalEnvironnementAction = toNormalEnvironnementAction;
	}



}